package org.jugile.daims;

/*

Copyright (C) 2011-2011 Jukka Rahkonen  email: jukka.rahkonen@iki.fi

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

*/

import java.io.Writer;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.log4j.Logger;
import org.jugile.daims.anno.ConnectionNN;
import org.jugile.util.Buffer;
import org.jugile.util.CsvTokenizer;
import org.jugile.util.DBConnection;
import org.jugile.util.DBQueue;
import org.jugile.util.Jugile;
import org.jugile.util.Msg;


/**
 * <i>"this is verse"</i> <b>()</b>
 * 
 * <br/>==========<br/>
 *
 * here is doc
 *  
 * @author jukka.rahkonen@iki.fi
 *
 */
public class DomainData extends Jugile {
	static Logger log = Logger.getLogger(DomainData.class);

//	private boolean writeThrough = false;
//	protected void setWriteThrough() { writeThrough = true; }
//	protected boolean isWriteThrough() { return writeThrough; }
	
	private DomainCore domain = null;
	protected DomainData(DomainCore domain) { this.domain = domain; }
	protected DomainCore d() { return domain; }
	
	protected BoCollection<Bo> getBoCollection(Class<Bo> cl) {
		BoCollection<Bo> bc = (BoCollection<Bo>)Jugile.instantiateObject(cl.getName()+"Collection");
		bc.origin = map(cl); // set coredata collection to its origin
		return bc;
	}
	

	protected Bo getOrCreate(Class<Bo> cl, String idstr) { 
		return getOrCreate(cl, parseLongSafe(idstr)); 
	}
	protected Bo getOrCreate(Class<Bo> cl, long id) {
		return map(cl).getOrCreateOld(id);
	}
	private Bo get(Class<Bo> cl, long id) {
		return map(cl).getOriginal(id);
	}
	
	private void remove(Class<Bo> cl, long id) {
		map(cl).remove(id);
	}
	
	// ----------- data maps -----------	
	private Map<Class<Bo>,BoMap<Bo>> maps = new HashMap<Class<Bo>,BoMap<Bo>>();
	private BoMap<Bo> map(Class<Bo> cl) {
		BoMap<Bo> m = maps.get(cl);
		if (m == null) {
			m = createBoMap(cl);
			maps.put(cl,m);
		}
		return m;
	}

	private BoMap<Bo> createBoMap(Class<Bo> cl) {
		BoMap<Bo> bm = new BoMap<Bo>(cl,this);
		return bm;
	}
	

	// ---------- persistence ----------
	protected int modifyDomain(CsvTokenizer t) throws Exception {
		int count = 0;
		List<String> headers = null;
		boolean mapping = false;
		Class<Bo> clo = null;
		String cn1 = null;
		//Proxy ddp = new Proxy(_dd());
		while (t.readLine()) {
			if ((count+1)%1000 == 0) log.info("modify domain: " + (count+1));
			if (t.col(0).startsWith(Bo.HEADERSTART)) {
				// headerline
				String cname = t.col(0).substring(1);
				clo = (Class<Bo>)getClassByName(cname);
				headers = new ArrayList<String>();
				for (String hdr : t.getCols()) headers.add(hdr);
				mapping = false;
				continue;
			}
			if (t.col(0).equals(Bo.MAPSTART)) {
				// n-n mapping
				clo = (Class<Bo>)getClassByName(t.col(1));
				cn1 = t.col(2);
				mapping = true;
				continue;
			}
			if (mapping) {
				if (t.col(0).equals(Bo.MAPSTART+"D")) {
					//log.debug("MAP DELETE");
					Bo o = get(clo, parseLongSafe(t.col(1)));
					if (o == null) {
						// this is not necessarily error case: if object is
						// just deleted and its n-n connection are put 
						// in n-n delete list, the object DEL can come first, 
						// then this is not found anymore, but its not an error.
						//fail("n-n object not found: " + clo + " " + t.col(1));
						continue;
					}
					for (int i = 2; i < t.cols(); i++) {
						o.removeOriginNN(cn1, parseLongSafe(t.col(i)));
					}
				} else {
					Bo o = get(clo, parseLongSafe(t.col(0)));
					if (o == null) fail("n-n object not found: " + clo + " " + t.col(0));
					Class<Bo> clo2 = o.getOtherEndClass(cn1); 
					for (int i = 1; i < t.cols(); i++) {
						Bo o2 = getOrCreate(clo2,t.col(i));
						o.addOriginNN(cn1, o2);
					}
				}
			} else {
				// dataline
				if (Bo.DEL.equals(t.col(0))) {
					//print("DELETE: " + t.col(1));
					try {
						// call onDeleteEvent
						Bo o = get(clo,parseLongSafe(t.col(1)));
						if (o != null) {
							o.onDeleteEvent();
							// cleanup all neighbour collections
							o.cleanUpOriginConnections();
						}
					} catch (Exception e) {}
					remove(clo,parseLongSafe(t.col(1))); // just remove from map					
				} else {
					Bo o = (Bo)getOrCreate(clo,t.col(0));
					if (o == null) fail("invalid object: "+ clo + " "+ t.col(0));
					o.parse(this, headers, t.getCols());
					//log.debug("Bo parsed: "+ o);
				}
			}
			count++;
		}
		return count;
	}
	
	protected void saveToCsv(Writer out) throws Exception {
		for (Class<Bo> cl : maps.keySet()) {
			//log.debug("writing csv: " + cl.getName());
			out.write(map(cl).csv());
			out.write(map(cl).nncsv());
		}
		out.close();
	}

	protected int saveToDB(DBConnection c) throws Exception {
		int count = 0;
		for (Class<Bo> cl : maps.keySet()) {
			log.info("writing db: " + cl.getName());
			int i = map(cl).writeAllToDB(c);
			log.info("  wrote rows: " + i);
			count += i;
		}		
		return count;
	}


	protected int loadFromDB(DBConnection c, Class<Bo>[] cls) throws Exception {
		int count = 0;
		for (Class<Bo> cl : cls) {
			log.debug("load table: " + cl);
			Bo o = (Bo)cl.newInstance(); // just for template
			BoInfo bi = o.bi();
			c.prepare("select "+o._getSelectFlds()+" from "+o.table()+" where archived=0");			
			for (List<Object> row : c.select()) {
				long id = (Integer)row.get(0);
				o = getOrCreate(cl,id);
				o._setState(this,bi,row);
				//log.debug("row: " + o);
				count++;
				if ((count%1000)==0) log.info("load " + bi.table + " "+ count);
			}
			// n-n connections
			for (Field f : bi.nns) {
				String nntable = f.getAnnotation(ConnectionNN.class).table();
				Class<Bo> cl2 = o.getOtherEndClass(f.getName());
				c.prepare("select o1,o2 from " + nntable);
				for (List<Object> row : c.select()) {
					long id1 = (Integer)row.get(0); 
					long id2 = (Integer)row.get(1);
//					Bo o1 = getOrCreate(cl,id1);
//					Bo o2 = getOrCreate(cl2,id2);
//					log.debug("NN: " + nntable + " " + o1 + " - " + o2);
					Bo o1 = getOrCreate(cl,id2); // SWAP - ANG needs this
					Bo o2 = getOrCreate(cl2,id1);
					o1.addOriginNN(f.getName(), o2);
					//log.debug("added NN: " + o1 + " --- " + o2);
				}
			}
		}
		return count;
	}

	
	protected int readDeltasFromQueue(String node, int max) throws Exception {
		int count = 0;
		for (Msg m : DBQueue.getMessages(node, max)) {
			String delta = m.getMsg();
			CsvTokenizer t = CsvTokenizer.parseString(delta,Bo.CSVDELIMITER);
			modifyDomain(t);
			count++;
		}
		return count;
	}

	
	protected void dump(Buffer buf) throws Exception {
		buf.ln("DomainData:");
		buf.ln("===========");
		for (BoMap m : maps.values()) m.dump(buf);
	}
	
	protected void stats(Stats st) {
		for (Class cl : st.classes()) { 
			map(cl).stats(st);
		}
	}
	
}

